import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

public class LoadBalancer {
    private static int countGetQuery;
    private static ArrayList<ServerDescriptor> serverList;

    private LoadBalancer() {
        serverList = new ArrayList<ServerDescriptor>();
        countGetQuery = 0;
    }

    public static LoadBalancer getInstance() {
        return new LoadBalancer();
    }

    public String execute(String strQuery) {
        try {
            Query query = new Query(strQuery);
            String result = "";
            String[] parameters = query.getQueryParameters();
            switch (query.getQueryType()) {
                case ADD_MASTER:
                    result = addNewMaster(query, parameters);
                    break;
                case ADD_SLAVE:
                    result = addNewSlave(parameters);
                    break;
                case GET:
                    result = getQueryExecute(query);
                    break;
                case PUT:
                case REMOVE:
                case UPDATE:
                    result = changeDataQueryExecute(query);
                    break;
                default:
                    result = "Error!";
            }
            return result;
        } catch (Exception e) {
            return "Error: " + e.getMessage();
        }
    }


    private String addNewMaster(Query query, String ... parameters) throws IOException, ClassNotFoundException {
        if (parameters.length < 3 || parameters.length > 4) {
            throw new IllegalArgumentException("Illegal count of query parameters!");
        }
        String result;
        InetAddress address = InetAddress.getByName(parameters[0]);
        int port = Integer.parseInt(parameters[1]);

        if (parameters.length == 3) {
            char keyFrom = parameters[2].charAt(0);
            ServerDescriptor oldMaster = locateNeedMaster(keyFrom);
            char keyTo = oldMaster.keyTo;

            ServerDescriptor newMasterDescriptor = new ServerDescriptor(address, port, true, keyFrom, keyTo);
            Postman.send(newMasterDescriptor.address, newMasterDescriptor.port, newMasterDescriptor);
            result = Postman.send(oldMaster.address, oldMaster.port, new Query("move data " + parameters[0] + " " + parameters[1] + " " + keyFrom + " " + keyTo));

            serverList.add(newMasterDescriptor);
            oldMaster.keyTo = (char) (newMasterDescriptor.keyFrom - 1);
        } else {
            char keyFrom = parameters[2].charAt(0);
            char keyTo = parameters[3].charAt(0);

            ServerDescriptor newMasterDescriptor = new ServerDescriptor(address, port, true, keyFrom, keyTo);
            result = Postman.send(newMasterDescriptor.address, newMasterDescriptor.port, newMasterDescriptor);

            serverList.add(newMasterDescriptor);
        }
        return result;
    }

    private String addNewSlave(String ... parameters) throws UnknownHostException {
        if (parameters.length != 4) {
            throw new IllegalArgumentException("Adding slave error: command parameters count");
        }
        InetAddress slaveAddress = InetAddress.getByName(parameters[0]);
        int slavePort = Integer.parseInt(parameters[1]);

        InetAddress masterAddress = InetAddress.getByName(parameters[2]);
        int masterPort = Integer.parseInt(parameters[3]);

        ServerDescriptor master = getMaster(masterAddress, masterPort);
        ServerDescriptor slaveDescriptor = new ServerDescriptor(slaveAddress, slavePort, false, master.keyFrom, master.keyTo);

        serverList.add(slaveDescriptor);

        return Postman.send(masterAddress, masterPort, new Query("add slave " + parameters[0] + " " + parameters[1]));
    }

    private String getQueryExecute(Query query) {
        List<ServerDescriptor> list = locateNeedServers(query.getQueryParameters()[0].charAt(0));
        ServerDescriptor descriptor = list.get(countGetQuery++ % list.size());
        return Postman.send(descriptor.address, descriptor.port, query);
    }

    private String changeDataQueryExecute(Query query) {
        ServerDescriptor master = locateNeedMaster(query.getQueryParameters()[0].charAt(0));
        return Postman.send(master.address, master.port, query);
    }

    private ServerDescriptor getMaster(InetAddress address, int  port) {
        for (ServerDescriptor descriptor : serverList) {
            if (descriptor.isMaster && descriptor.address.equals(address) && descriptor.port == port) {
                return descriptor;
            }
        }
        throw new IllegalArgumentException("There is no master server with such address or port!");
    }

    private ServerDescriptor locateNeedMaster(char ch) {
        for (ServerDescriptor descriptor : serverList) {
            if (descriptor.isMaster && descriptor.keyFrom <= ch && descriptor.keyTo >= ch) {
                return descriptor;
            }
        }
        throw new IllegalArgumentException("There is no master server with such keys!");
    }

    private List<ServerDescriptor> locateNeedServers(char ch) {
        List<ServerDescriptor> list = new ArrayList<ServerDescriptor>();
        for (ServerDescriptor descriptor : serverList) {
            if (descriptor.keyFrom <= ch && descriptor.keyTo >= ch) {
                list.add(descriptor);
            }
        }
        if (list.isEmpty()) {
            throw new IllegalArgumentException("There are no servers with such keys!");
        }
        return list;
    }
}

